// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#pragma once

#include <functional>
#include <string>
#include "melon/likely.h"
#include <pollux/common/base/exceptions.h>

namespace kumo::pollux {
    template<typename T>
    T checkedPlus(const T &a, const T &b, const char *typeName = "integer") {
        T result;
        bool overflow = __builtin_add_overflow(a, b, &result);
        if (UNLIKELY(overflow)) {
            POLLUX_ARITHMETIC_ERROR("{} overflow: {} + {}", typeName, a, b);
        }
        return result;
    }

    template<typename T>
    T checkedMinus(const T &a, const T &b, const char *typeName = "integer") {
        T result;
        bool overflow = __builtin_sub_overflow(a, b, &result);
        if (UNLIKELY(overflow)) {
            POLLUX_ARITHMETIC_ERROR("{} overflow: {} - {}", typeName, a, b);
        }
        return result;
    }

    template<typename T>
    T checkedMultiply(const T &a, const T &b, const char *typeName = "integer") {
        T result;
        bool overflow = __builtin_mul_overflow(a, b, &result);
        if (UNLIKELY(overflow)) {
            POLLUX_ARITHMETIC_ERROR("{} overflow: {} * {}", typeName, a, b);
        }
        return result;
    }

    template<typename T>
    T checkedDivide(const T &a, const T &b) {
        if (b == 0) {
            POLLUX_ARITHMETIC_ERROR("division by zero");
        }

        // Type T can not represent abs(std::numeric_limits<T>::min()).
        if constexpr (std::is_integral_v<T>) {
            if (UNLIKELY(a == std::numeric_limits<T>::min() && b == -1)) {
                POLLUX_ARITHMETIC_ERROR("integer overflow: {} / {}", a, b);
            }
        }
        return a / b;
    }

    template<typename T>
    T checkedModulus(const T &a, const T &b) {
        if (UNLIKELY(b == 0)) {
            POLLUX_ARITHMETIC_ERROR("Cannot divide by 0");
        }
        // std::numeric_limits<int64_t>::min() % -1 could crash the program since
        // abs(std::numeric_limits<int64_t>::min()) can not be represented in
        // int64_t.
        if (b == -1) {
            return 0;
        }
        return (a % b);
    }

    template<typename T>
    T checkedNegate(const T &a) {
        if (UNLIKELY(a == std::numeric_limits<T>::min())) {
            POLLUX_ARITHMETIC_ERROR("Cannot negate minimum value");
        }
        return std::negate<std::remove_cv_t<T> >()(a);
    }
} // namespace kumo::pollux
